﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using FibMatrix.DS;
using FibMatrix.IO;
using FibMatrix.Utils;
using UnityEngine;
using UnityEngine.Profiling;

namespace FibMatrix
{
    /// <summary>
    /// AssetBundle资源模式的实现
    /// </summary>
    [FM_Mono.IgnoreGen]
    public class BundleResourceFacadeImpl : AbstractResourceFacadeImpl
    {
        public const string PROJECT_DYNAMIC_RESOURCE_ROOT = "Assets/BundleResources/";

        public const string BUNDLE_OFFSET_TABLE_FILE =
#if UNITY_WEBGL
            // 微信小游戏只读分包里不允许.bytes后缀，有个白名单。cdn对txt可设置压缩
            // https://developers.weixin.qq.com/minigame/dev/guide/framework/code-package.html
            "BundleOffsetTable.dat";
#else
			"BundleOffsetTable.bytes"; 
#endif
		

        private ObjectPool2<ResourcePipelineContext> _contextPool = new ObjectPool2<ResourcePipelineContext>(-1, () => new ResourcePipelineContext());

        // bundle map, bundleUrl -> obj
        private Dictionary<string, BundleInfo> _bundleHintMap = new Dictionary<string, BundleInfo>();

        private AssetList _assetConfig = null;

        private bool _unloadWhenDispose = false;

        public override string GenerateLoadURL(string prefix, string localPath, string extension)
        {
            return PROJECT_DYNAMIC_RESOURCE_ROOT + prefix + localPath + extension;
        }

        public override bool CanUseLoadSync(string assetId)
        {
            var fileNodes = ResourceHelper.GetAllDependenciesNodeByAssets(
                AssetConfig.defaultConfig.resList, new[] { assetId });
            foreach (var v in fileNodes)
            {
                if (BundleLoadManager.instance.DoesBundleExist(v.url, v.md5) == false)
                    return false;
            }

            return true;
        }

#if !DISABLE_SYNC_LOAD
        public override T[] LoadAllAssetOnce<T>(string assetId)
        {
            var item = this.AcquireLoadItem(assetId, true) as AssetBundleLoadItem;
            if (item != null)
            {
                BundleLoadHandle bundleHandle = BundleLoadManager.instance.LoadBundle(item.bundleInfo.url, item.bundleInfo.md5/*, item.id*/);
                if (bundleHandle != null && bundleHandle.bundle)
                {
                    Profiler.BeginSample("BundleResourceFacadeImpl.LoadAllAssets");
                    var res = bundleHandle.bundle.LoadAllAssets<T>();
                    Profiler.EndSample();
                    return res;
                }
            }
            return null;
        }
#endif

        private IBundleFileMapper LoadBundleOffsetMapper(SearchPath searchPath, byte[] otBytes)
        {
            if (otBytes == null)
                return new DefaultOffsetMapper(searchPath, null, null);
            
            string[] fragmentPathList = null;
            byte[] tableBytes = null;
            using (MemoryStream ms = new MemoryStream(otBytes))
            using (BinaryReader br = new BinaryReader(ms))
            {
                var fragmentPathCount = br.ReadInt32();
                if (fragmentPathCount > 0)
                {
                    fragmentPathList = new string[fragmentPathCount];
                    for (int i = 0; i < fragmentPathCount; ++i)
                    {
                        fragmentPathList[i] = br.ReadString();
                    }
                }

                var tableBytesLen = br.ReadInt32();
                if (tableBytesLen > 0)
                {
                    tableBytes = br.ReadBytes(tableBytesLen);
                }
            }

            return new DefaultOffsetMapper(searchPath, fragmentPathList, tableBytes);
        }

        protected override void DoInitialize(IResourceInitConfig initConfig)
        {
            AssetBundleInitConfig bundleInitConfig = initConfig as AssetBundleInitConfig;

            _loadItemMap.Clear();
            _bundleHintMap.Clear();

            _assetConfig = bundleInitConfig.config.resList;

            _unloadWhenDispose = bundleInitConfig.unloadWhenDispose;

            var searchPaths = bundleInitConfig.GetSearchPathList();

            // SearchPath packageBundleSearchPath = null;  
            // byte[] splitBundleRawBytes = StreamingAssetReader.ReadStreamingAsset("splitBundle.txt");
            // if (splitBundleRawBytes != null)
            // {
            //     string splitBundleStr = Encoding.UTF8.GetString(splitBundleRawBytes);
            //     if (!string.IsNullOrEmpty(splitBundleStr))
            //     {
            //         var replacePathArr = splitBundleStr.Split('^');
            //         if (replacePathArr.Length == 2)
            //         {
            //             string bundleSearchPrefix = bundleInitConfig.streamingAssetsPath + "/";
            //             bundleSearchPrefix = bundleSearchPrefix.Replace(replacePathArr[0], replacePathArr[1]);
            //             packageBundleSearchPath =
            //                 new SearchPath(bundleSearchPrefix, PersistType.Package);
            //         }
            //     }
            // }

            if (searchPaths.Count > 2)
            {
                throw new Exception("Can't support bundle search path size > 2");
            }

            SearchPath packageBundleSearchPath = null;
            foreach (var path in searchPaths)
            {
                if (path.persistType == PersistType.Package)
                {
                    packageBundleSearchPath = path;
                    break;
                }
            }

            byte[] bundleOffsetTableBytes =
#if UNITY_WEBGL && !UNITY_EDITOR
                null;   // webgl不需要读，但需要有mapper不报异常。未来可以改成不需要mapper。编辑器里没预下载还是需要读。
#else
                PackageManager.ReadAllBytes(BUNDLE_OFFSET_TABLE_FILE);
#endif
            
            BundleLoadManager.instance.Initialize(searchPaths, 
                LoadBundleOffsetMapper(packageBundleSearchPath, bundleOffsetTableBytes));
            
            
            // AssetList assetList = bundleInitConfig.config.bundleList;
            // int nodeCount = assetList.nodeCount;
            // for (int loop = 0; loop < nodeCount; ++loop)
            // {
            //     var key = assetList[loop].id;
            //     var val = assetList[loop];
            //     AssetBundleLoadItem hint = this.AcquireLoadItem(key, false) as AssetBundleLoadItem;
            //     if (hint == null)
            //     {
            //         hint = new AssetBundleLoadItem();
            //         _bundleHintMap.Add(key, hint);
            //     }
            //     hint.id = val.id;
            //
            //     int[] oriDepIdxList = val.dependIndexes;
            //     int count = oriDepIdxList != null ? oriDepIdxList.Length : 0;
            //     if (count > 0)
            //     {
            //         List<AssetBundleLoadItem> depList = hint.dependence = new List<AssetBundleLoadItem>(count);
            //         for (int i = 0; i < count; ++i)
            //         {
            //             string id = assetList[oriDepIdxList[i]].id;
            //             AssetBundleLoadItem childHint = this.AcquireLoadItem(id, false) as AssetBundleLoadItem; ;
            //             if (childHint == null)
            //             {
            //                 childHint = new AssetBundleLoadItem();
            //                 childHint.id = id;
            //                 _bundleHintMap.Add(id, childHint);
            //             }
            //             childHint.otherDepend = true;
            //             depList.Add(childHint);
            //         }
            //     }
            //
            //     if (val.dynamic)
            //     {
            //         hint.node = val;
            //         hint.url = val.url;
            //         hint.urlMode = AssetBundleUrlMode.Remote;
            //         preloadNodes.Add(val);
            //     }
            //     else
            //     {
            //         if (bundleInitConfig.forceExtractFromPackage || val.extractFromPackage)
            //         {
            //             hint.url = streamingPath + val.url;
            //             hint.urlMode = AssetBundleUrlMode.Package;
            //         }
            //         else
            //         {
            //             hint.url = persistentDataPath + val.url;
            //             hint.urlMode = AssetBundleUrlMode.Local;
            //         }
            //     }
            // }

            // ResourceDownloadManager.instance.Init(bundleInitConfig.dynamicAssets ?? new HashSet<string>(), _assetConfig, bundleInitConfig.cdnList, bundleInitConfig.persistentDataPath, bundleInitConfig.temporaryCachePath);
        }

        protected override ResourcePipelineContext CreatePipelineContext()
        {
            return _contextPool.Allocate();
        }

        protected override void DestroyPipelineContext(ResourcePipelineContext context)
        {
            _contextPool.Recycle(context);
        }

        protected override List<IResourceLoadPipe> CreateSyncLoadPipes(IResourceInitConfig initConfig)
        {
            return new List<IResourceLoadPipe>
                {
                    // new FlatBundleDependencePipe(),
                    new LoadBundleFromDiskPipe(),
                    new LoadAssetFromBundlePipe()
                };
        }

        protected override List<IResourceLoadPipe> CreateAsyncLoadPipes(IResourceInitConfig initConfig)
        {
            return new List<IResourceLoadPipe>
                {
                    // new FlatBundleDependencePipe(),
#if UNITY_WEBGL && !UNITY_EDITOR
                    // 编辑器中短时间不控制并发的大量webRequest会有时导致unity cpu100%卡一阵子
                    new AsyncLoadBundleFromRemotePipe((initConfig as AssetBundleInitConfig).cdn),
#else
                    new DownLoadBundlePipe(),
                    new AsyncLoadBundleFromDiskPipe(),
#endif
                    new AsyncLoadAssetFromBundlePipe()
                };
        }

        protected override AbstractLoadItem AcquireLoadItem(string assetId, bool createIfNotExist)
        {
            if (!_loadItemMap.TryGetValue(assetId, out var assetItem) && createIfNotExist)
            {
                if (!_assetConfig.TryGetValue(assetId, out var assetNode))
                {
                    Debug.LogError($"asset not exist:{assetId}");
                    return null;
                }

                // 需要创建asset item和bundle info
                var bundleInfo = AcquireBundleInfoRecursive(assetNode);
                assetItem = new AssetBundleLoadItem(assetId, bundleInfo);
                _loadItemMap.Add(assetId, assetItem);

            }
            // 资源首次被需要，或卸载后首次被需要。要把直接依赖展开为全依赖，并对依赖的bundle加计数
            if (assetItem != null && assetItem.objRefCount == 0)
            {
                var bundleInfo = (assetItem as AssetBundleLoadItem).bundleInfo;
                if (bundleInfo.flatDependence == null && bundleInfo.dependence != null &&
                    bundleInfo.dependence.Count > 0)
                    bundleInfo.flatDependence = ResourceHelper.ProcessLoadDependence(bundleInfo);

                List<BundleInfo> dependence = ResourceHelper.GetFixedFlatDependences(bundleInfo);
                for (int i = dependence.Count - 1; i >= 0; --i)
                {
                    BundleInfo hint = dependence[i];
                    ++hint.bundleRefCount;
                    AssetActionLogger.RecordAction(null, hint.url, AssetBundleActionType.BundItemInc,
                        $"BRef={hint.bundleRefCount}");
                }
            }
            // TODO , 改为这里增加资源计数

            return assetItem;
        }
        
        /// <summary>
        /// 创建bundle对应的加载信息
        /// </summary>
        /// <param name="node"></param>
        /// <param name="createIfNotExist"></param>
        /// <returns></returns>
        BundleInfo AcquireBundleInfoRecursive(AssetNode node)
        {
            if (!_bundleHintMap.TryGetValue(node.url, out var bundleInfo))
            {
                bundleInfo = new BundleInfo(node);
                _bundleHintMap.Add(bundleInfo.url, bundleInfo);

                int[] oriDepIdxList = node.dependIndexes;
                int count = oriDepIdxList?.Length ?? 0;
                if (count > 0)
                {
                    List<BundleInfo> depList = bundleInfo.dependence = new List<BundleInfo>(count);
                    for (int i = 0; i < count; ++i)
                    {
                        var depNode = _assetConfig[oriDepIdxList[i]];
                        BundleInfo childHint = AcquireBundleInfoRecursive(depNode);
                        depList.Add(childHint);
                    }
                }
            }
            return bundleInfo;
        }

        protected override bool ReleaseLoadItem(AbstractLoadItem rawItem)
        {
            var item = rawItem as AssetBundleLoadItem;
            RemoveInstanceIdFromLoadItem(item);

            item.objRefCount = 0;
            item.instanceID = -1;
            bool needUnloadUnusedAsset = false;
            if (item.assetHandle != null)
            {
                needUnloadUnusedAsset = true;
                item.bundleHandle.UnloadAsset(item.assetHandle);
                item.assetHandle = null;
            }

            var dependences = ResourceHelper.GetFixedFlatDependences(item.bundleInfo);

            // 释放、卸载资源时才减对应bundle的计数
            for (int i = dependences.Count - 1; i >= 0; --i)
            {
                var dItem = dependences[i];
                --dItem.bundleRefCount;
				AssetActionLogger.RecordAction(null, dItem.url, AssetBundleActionType.BundItemDec, 
					$"BRef={dItem.bundleRefCount}");
                
                if (dItem.bundleRefCount < 0)
                    Debug.LogError("Bundle" + dItem.url + " over release! Load item is " + item.itemID);

                if (dItem.bundleRefCount == 0)
                {
                    needUnloadUnusedAsset = !RecycleBundle(dItem) || needUnloadUnusedAsset;
                }
            }

            return needUnloadUnusedAsset;
        }


        protected override void UnloadAll()
        {
            _loadItemMap.Clear();
            _bundleHintMap.Clear();
            BundleLoadManager.instance.UnloadAll(this._unloadWhenDispose);
            AssetBundle.UnloadAllAssetBundles(this._unloadWhenDispose);
        }

        private bool RecycleBundle(BundleInfo hint)
        {
            if (hint.bundleHandle != null)
            {
                BundleLoadManager.instance.UnloadBundle(hint.bundleHandle, true);
                hint.bundleHandle = null;
                return true;
            }
            return false;
        }
    }
}
